// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Package doc从Go AST中提取源代码文档。
package doc

import (
	"fmt"
	"go/ast"
	"go/token"
	"strings"
)

// 包是整个包的文档。
type Package struct {
	Doc        string
	Name       string
	ImportPath string
	Imports    []string
	Filenames  []string
	Notes      map[string][]*Note

	// 不推荐使用：由于向后兼容性错误仍然存在，
	// 但是所有新代码都应该使用Notes。
	Bugs []string

	// 声明
	Consts []*Value
	Types  []*Type
	Vars   []*Value
	Funcs  []*Func

	// 示例是与
	// 包相关的示例的排序列表。示例摘自_测试。go files 
	// 提供给NewFromFiles。
	Examples []*Example
}

// Value是（可能已分组）var或const声明的文档。
type Value struct {
	Doc   string
	Names []string // var或const名称的声明顺序
	Decl  *ast.GenDecl

	order int
}

// Type是类型声明的文档。
type Type struct {
	Doc  string
	Name string
	Decl *ast.GenDecl

	// 关联声明
	Consts  []*Value // 此类型（主要）常量的排序列表
	Vars    []*Value // 此类型（主要）变量的排序列表
	Funcs   []*Func  // 返回此类型的函数的排序列表
	Methods []*Func  // 此类型的方法（包括嵌入方法）的排序列表

	// 示例是关联示例的排序列表使用
	// 此类型。示例摘自_测试。go files 
	// 提供给NewFromFiles。
	Examples []*Example
}

// Func是Func声明的文档。
type Func struct {
	Doc  string
	Name string
	Decl *ast.FuncDecl

	// 方法
	// （对于函数，这些字段具有各自的零值）
	Recv  string // 实际接收器“T”或“*T”
	Orig  string // 原始接收器“T”或“*T”
	Level int    // 嵌入级别；0表示未嵌入

	// 示例是与此
	// 函数或方法关联的示例的排序列表。示例摘自_测试。go files 
	// 提供给NewFromFiles。
	Examples []*Example
}

// 注释表示以“MARKER（uid）：Note body”开头的标记注释。
// 任何带有2个或更多大写字母[a-Z]标记且uid为
// 的注释，至少识别一个字符。uid后面的“：”是可选的。
// 包裹中收集了笔记。注释映射由注释标记索引。
type Note struct {
	Pos, End token.Pos // 包含标记
	UID      string    // 使用标记
	Body     string    // 注释正文
}

// 模式值控制New和NewFromFile的操作。
type Mode int

const (
	// AllDecls表示要提取所有包级
	// 声明的文档，而不仅仅是导出的声明。
	AllDecls Mode = 1 << iota

	// AllMethods表示显示所有嵌入的方法，而不仅仅是
	// 不可见（未报告）的匿名字段。
	AllMethods

	// AST要求保持AST不变。最初，在
	// godoc中，为了节省内存，AST（如函数体）被置零，但并非所有程序都希望这样做。
	PreserveAST
)

// New计算给定包AST的包文档。
// New拥有AST pkg的所有权，并可能对其进行编辑或覆盖。
// 要填充示例字段，请使用NewFromFiles并包含
// 包的_测试。去查档案。
// 
func New(pkg *ast.Package, importPath string, mode Mode) *Package {
	var r reader
	r.readPackage(pkg, mode)
	r.computeMethodSets()
	r.cleanupTypes()
	return &Package{
		Doc:        r.doc,
		Name:       pkg.Name,
		ImportPath: importPath,
		Imports:    sortedKeys(r.imports),
		Filenames:  r.filenames,
		Notes:      r.notes,
		Bugs:       noteBodies(r.notes["BUG"]),
		Consts:     sortedValues(r.values, token.CONST),
		Types:      sortedTypes(r.types, mode&AllMethods != 0),
		Vars:       sortedValues(r.values, token.VAR),
		Funcs:      sortedFuncs(r.funcs, true),
	}
}

// NewFromFiles计算包的文档。
// 
// 包由*ast列表指定。文件和相应的
// 文件集，不能为零。
// NewFromFiles在计算文档时使用所有提供的文件，
// 因此调用方有责任只提供与所需构建上下文匹配的文件。“去/建造”。上下文MatchFile可用于确定文件是否与构建上下文匹配，以及所需的GOOS和GOARCH值以及其他构建约束。
// 包的导入路径由importPath指定。
// 
// 在_测试中发现的示例。go文件根据其名称与相应的
// 类型、函数、方法或包相关联。
// 如果示例名称中有后缀，则在
// 示例中设置。后缀字段。将跳过名称格式错误的示例。
// 
// 可选地，可以为
// 控制文档提取行为的低级方面提供一个额外的Mode类型参数。
// 
// NewFromFiles拥有AST文件的所有权，并且可以对其进行编辑，
// 除非保留AST模式位处于启用状态。
// 
func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...any) (*Package, error) {
	// 检查API使用是否无效。
	if fset == nil {
		panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)"))
	}
	var mode Mode
	switch len(opts) { // 只能有0或1个选项，所以现在可以使用一个简单的开关。
	case 0:
		// 无事可做。
	case 1:
		m, ok := opts[0].(Mode)
		if !ok {
			panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode"))
		}
		mode = m
	default:
		panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument"))
	}

	// 收集。去测试一下。去查档案。
	var (
		goFiles     = make(map[string]*ast.File)
		testGoFiles []*ast.File
	)
	for i := range files {
		f := fset.File(files[i].Pos())
		if f == nil {
			return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i)
		}
		switch name := f.Name(); {
		case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"):
			goFiles[name] = files[i]
		case strings.HasSuffix(name, "_test.go"):
			testGoFiles = append(testGoFiles, files[i])
		default:
			return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, name)
		}
	}

	// TODO（dmitshur，gri）：对ast的一个相对较高级别的调用。带有simpleImporter 
	// ast的新软件包。进口商执行情况如下。它可能会短路并简化。

	// 计算包文档。
	pkg, _ := ast.NewPackage(fset, goFiles, simpleImporter, nil) // 忽略由于未解析标识符而可能发生的错误。
	p := New(pkg, importPath, mode)
	classifyExamples(p, Examples(testGoFiles...))
	return p, nil
}

// simpleImporter返回一个（虚拟）包对象，该对象由提供的包路径的最后一个路径
// 组件命名（与包的约定相同）。
// 这足以解析包标识符，而无需执行实际的
// 导入。它从不返回错误。
func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
	pkg := imports[path]
	if pkg == nil {
		// 注意字符串。如果ast不需要“/”
		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
		pkg.Data = ast.NewScope(nil) // 则LastIndex返回-1。dot进口
		imports[path] = pkg
	}
	return pkg, nil
}
